#include "GUIManager.h"
#include <assert.h>

namespace
{
	struct delete_disposer
	{
		template<typename T>
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};
}

GUIManager* GUIManager::instance = NULL;

GUIManager::GUIManager(const sf::Input& input)
:input(input),dragging(false)
{
	assert(instance == 0);
	instance = this;
	SwfUI::initialize();
}

GUIManager::~GUIManager()
{
	instance = NULL;
	controls.clear_and_dispose(delete_disposer());
	SwfUI::shutdown();
}

bool GUIManager::processEvent(const sf::Event& ev)
{
	switch(ev.Type)
	{
	case sf::Event::MouseButtonPressed:
		return mouseDown(ev);
	case sf::Event::MouseButtonReleased:
		return mouseUp(ev);
	case sf::Event::MouseMoved:
		return mouseMoved(ev);
	case sf::Event::MouseWheelMoved:
		return mouseWheel(ev);
	case sf::Event::KeyPressed:
	case sf::Event::KeyReleased:
		if(!SwfUI::getFocus())//no control to receive event
			return false;
		else
			return true;//the control consumes this event
	default:
		return false;//don't care about other types of events
	}
}

GUIControl* GUIManager::findControlAt(int x, int y)
{
	for(ControlCollection::iterator i = controls.begin(); i!= controls.end();)
	{
		if(i->markedForDeletion)
		{
			i = controls.erase_and_dispose(i,delete_disposer());
			continue;
		}

		if(i->isPointInControl(x,y))
			return &(*i);

		++i;
	}

	return NULL;
}

void GUIManager::bringToFront(GUIControl* control)
{
	controls.erase(controls.iterator_to(*control));
	controls.push_front(*control);
}

void GUIManager::sendToBack(GUIControl* control)
{
	controls.erase(controls.iterator_to(*control));
	controls.push_back(*control);
}

bool GUIManager::mouseDown(const sf::Event& ev)
{
	GUIControl* clickedControl = findControlAt(ev.MouseButton.X,ev.MouseButton.Y);
	if(clickedControl)
	{
		if(ev.MouseButton.Button == sf::Mouse::Left)//if it is left mouse
		{
			SwfUI::setFocus(clickedControl);
			bringToFront(clickedControl);
			clickedControl->injectMouse(SwfUI::MET_MOUSELDOWN,ev.MouseButton.X, ev.MouseButton.Y, 0);

			int x = ev.MouseButton.X;
			int y = ev.MouseButton.Y;
			clickedControl->transformToLocal(x,y);
			if(y <= clickedControl->titleBarHeight)
			{
				dragging = true;
				oldX = ev.MouseButton.X;
				oldY = ev.MouseButton.Y;
			}
		}
		return true;//consume the event anyway
	}
	else
	{
		SwfUI::setFocus(NULL);
		return false;
	}
}

bool GUIManager::mouseUp(const sf::Event& ev)
{
	dragging = false;
	GUIControl* clickedControl = findControlAt(ev.MouseButton.X,ev.MouseButton.Y);
	if(clickedControl)
	{
		if(ev.MouseButton.Button == sf::Mouse::Left)//if it is left mouse
		{
			clickedControl->injectMouse(SwfUI::MET_MOUSELUP,ev.MouseButton.X, ev.MouseButton.Y, 0);
		}
		return true;
	}
	else
		return false;
}

bool GUIManager::mouseMoved(const sf::Event& ev)
{
	if(dragging)
	{
		GUIControl* control = static_cast<GUIControl*>(SwfUI::getFocus());
		if(control)
		{
			control->Move(ev.MouseMove.X - oldX, ev.MouseMove.Y - oldY);
			oldX = ev.MouseMove.X;
			oldY = ev.MouseMove.Y;
			return true;
		}
		else
		{
			dragging = false;//the dragged control died somehow
			return false;
		}
	}
	else
	{
		GUIControl* control = findControlAt(ev.MouseMove.X,ev.MouseMove.Y);
		if(control)
		{
			control->injectMouse(SwfUI::MET_MOUSEMOVED, ev.MouseMove.X, ev.MouseMove.Y, 0);
			return true;
		}
		else
			return false;
	}
}

bool GUIManager::mouseWheel(const sf::Event& ev)
{
	GUIControl* control = static_cast<GUIControl*>(SwfUI::getFocus());//only send to focused control
	if(control)
	{
		control->injectMouse(SwfUI::MET_MOUSEWHEEL,input.GetMouseX(),input.GetMouseY(),ev.MouseWheel.Delta);
		return true;
	}
	else
		return false;
}

GUIControl* GUIManager::createControl(int width, int height)
{
	GUIControl* control = new GUIControl(width,height);
	controls.push_front(*control);
	return control;
}

void GUIManager::removeControl(GUIControl* control)
{
	control->markedForDeletion = true;
}

void GUIManager::render(sf::RenderTarget& target) const
{
	sf::FlashControl::updateAll();

	for(ControlCollection::const_reverse_iterator i = controls.rbegin(); i != controls.rend();)
	{
		if(i->markedForDeletion)
		{
			i = ControlCollection::const_reverse_iterator(controls.erase_and_dispose(i.base(),delete_disposer()));
			continue;
		}

		target.Draw(*i);

		++i;
	}
}

void GUIManager::addFunction(const std::wstring& functionName, const ExternalFunction& function)
{
	externalFunctions[functionName] = function;
}

void GUIManager::removeFunction(const std::wstring& functionName)
{
	FunctionCollection::iterator i = externalFunctions.find(functionName);
	if(i != externalFunctions.end())
		externalFunctions.erase(i);
}

SwfUI::Argument GUIManager::callFunction(const std::wstring& functionName, GUIControl* caller, const SwfUI::ArgumentList& args)
{
	FunctionCollection::iterator i = externalFunctions.find(functionName);
	if(i == externalFunctions.end())
		return SwfUI::Argument::Null;
	else
	{
		ExternalFunction function = i->second;
		return function(caller,args);
	}
}